﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Cci;
using PartialExpressionCompleter.Query;
using PartialExpressionCompleter.Util;
using Logging;

namespace PartialExpressionCompleter.Experiment
{
    class MethodQueryExperiment : MethodExperiment
    {
        public override string FileExtension { get { return "mquery"; } }

        public MethodQueryExperiment(IAssembly unit)
            : base(unit)
        { }
        protected static IEnumerable<UnknownMethod> QueriesForCall(MethodCall call)
        {
            var args = call.Arguments;
            foreach (var arg in args)
            {
                yield return new UnknownMethod(
                    new List<Expression> { arg });
            }
            for (int i = 0; i < args.Count; i++)
            {
                for (int j = i + 1; j < args.Count; j++)
                {
                    yield return new UnknownMethod(
                        new List<Expression>() { args[i], args[j] });
                }
            }
        }
        protected static IEnumerable<UnknownMethod> QueriesForCallWithThis(
                                                            MethodCall call)
        {
            var queries = QueriesForCall(call)
                .Distinct(new ComparerOn<UnknownMethod, string>(
                    um => um.Arguments.Select(a => a.Type).CommaSeparated()));
            if (call.IsStatic)
            {
                return queries;
            }
            else
            {
                var recv = call.Arguments.First();
                return queries.Where(c => c.Arguments.Contains(recv));
            }
        }
        class QueryResult
        {
            private readonly UnknownMethod query;
            private readonly QueryContext<IMethodCall> context;
            private readonly MethodCall origCall;
            private readonly List<MethodCall> results;
            private readonly int correctRank, typedRank;
            private readonly TimeSpan time;

            private static readonly IEqualityComparer<MethodCall> methodNameComparer =
                new Util.ComparerOn<MethodCall, string>(mc => mc.Method.GetQualifiedName());

            public QueryResult(UnknownMethod query,
                               QueryContext<IMethodCall> context,
                               MethodCall origCall)
            {
                this.query = query;
                this.context = context;
                this.origCall = origCall;

                if (!context.IsMemberVisible(origCall.Method.ResolvedMethod))
                {
                    bool debug = context.IsMemberVisible(origCall.Method.ResolvedMethod);
                }

                var start = DateTime.Now;
                this.results = query.GetQueryResults(context)
                                    .Cast<MethodCall>()
                                    .Distinct(methodNameComparer)
                                    .ToList();
                var end = DateTime.Now;
                time = end - start;

                correctRank = RankInList(results);
                typedRank = RankInList(results
                    .Where(res => origCall.Type.ToString().Equals(res.Type.ToString())
                               || origCall.Type.IsAssignableFrom(res.Type, true)
                               || res.Type.IsAssignableFrom(origCall.Type, true))
                    .ToList());
            }
            private int RankInList(List<MethodCall> l)
            {
                var correctAnswer = origCall.Method.GetQualifiedName();
                var rank = l.Select(res => res.Method.GetQualifiedName())
                            .TakeWhile(r => !r.Equals(correctAnswer))
                            .Count() + 1;
                if (rank == l.Count + 1)
                {
                    rank = int.MaxValue;
                }
                return rank;
            }

            public UnknownMethod Query { get { return query; } }
            public IEnumerable<Expression> ArgSubset { get { return query.Arguments; } }
            public IEnumerable<MethodCall> AlternateMethods { get { return results; } }
            //public int CorrectAnswerScoreDifference { get { return correctAnswerScoreDiff; } }
            public int CorrectAnswerRank { get { return correctRank; } }
            public int CorrectTypedAnswerRank { get { return typedRank; } }
            public TimeSpan ComptuationTime { get { return time; } }
        }
        public static string RankToString(int rank)
        {
            if (rank == int.MaxValue)
            {
                return "FAIL";
            }
            else if (rank > 20)
            {
                return "BAD";
            }
            else if (rank > 10)
            {
                return "OKAY";
            }
            else
            {
                return "GOOD";
            }
        }
        protected override void DoMethodCallAction(QueryContext<IMethodCall> context)
        {
            /*
            var fresults = new QueryResult(new UnknownMethod(new List<Expression> { new LocalVariable("img", CCIUtil.GetType("PaintDotNet.Document")), new LocalVariable("size", CCIUtil.GetType("System.Drawing.Size")) }), context, context.OriginalExpressionAsQuery as MethodCall);
            foreach(var r in fresults.AlternateMethods.Take(10))
            {
                Logger.PrintLine(r.ToString());
            }
            Logger.PrintSeparator();
            */
            var call = context.OriginalExpression;
            var containingMethod = context.ContainingMethod;

            var startCalle = DateTime.Now;
            var calle = call.AsExpression(context) as MethodCall;
            Logger.PrintLine("Time elapsed for call expression (seconds): "
                    + (DateTime.Now - startCalle).TotalSeconds);
            Logger.PrintLine();

            // XXX DEBUG
            /*
            if (calle.Method.ContainingType.ToString()
                .StartsWith("Do.Platform.Log<")
                && calle.Method.Name.ToString().Equals("Error"))
            {
            }
            else
            {
                continue;
            }/*
            if (containingType.ToString().StartsWith("Microsoft.Tools.WindowsInstallerXml.WixGroupingOrdering.EnhancedKeyCollection")
                && calle.Method.Name.ToString().Equals("TryGetValue"))
            {
            }
            else
            {
                continue;
            }//*/

            /*
            foreach (var loc in call.Locations
                .Select(loc => loc as Microsoft.Cci.MetadataReader.MethodBodyLocation)
                .Where(loc => loc != null))
            {
                containingMethod = loc.MethodDefinition;
                containingType = containingMethod.ContainingType.ResolvedType;
            }*/

            Logger.PrintLine();
            Logger.PrintLine("Original call: " + calle.ToString()
                + (containingMethod == null ? "" : " (in " + containingMethod + ")"));
            Logger.PrintLine("Return type: " + calle.Type);
            string instStaticStr = calle.IsStatic ? "static" : "instance";
            Logger.PrintLine("Receiver type: "
                // TODO better way to do this? Does this actually work right?
                + "(" + instStaticStr + ") "
                + calle.Method.ContainingType);
            Logger.PrintLine("Argument count: " + calle.Arguments.Count);
            var allResults = QueriesForCallWithThis(calle)
                .Select(q => new QueryResult(q, context, calle))
                .ToList();
            var bestCorrectRankRes = allResults
                .OrderBy(r => r.CorrectAnswerRank)
                .First();
            var bestCorrectRank = bestCorrectRankRes.CorrectAnswerRank;
            string bestCorrectRankStr = RankToString(bestCorrectRank);
            var bestTypedRank = allResults.Min(r => r.CorrectTypedAnswerRank);
            string bestTypedRankStr = RankToString(bestTypedRank);
            Logger.PrintLine("Best correct answer rank: "
                + bestCorrectRank + " (" + bestCorrectRankStr + ")");
            Logger.PrintLine("Time elapsed for best correct answer rank: "
                + bestCorrectRankRes.ComptuationTime.TotalSeconds);
            Logger.PrintLine("Best typed correct answer rank: "
                + bestTypedRank + " (" + bestTypedRankStr + ")");

            var shortestRes = allResults
                .OrderBy(r => r.ArgSubset.Count().PairWith(r.CorrectAnswerRank));
            var shortestOkay = shortestRes
                .FirstOrDefault(r => r.CorrectAnswerRank <= 20);
            var shortestGood = shortestRes
                .FirstOrDefault(r => r.CorrectAnswerRank <= 10);

            List<IMethodDefinition> thisMethods;
            ITypeDefinition thisType;
            if (calle.Method.HasThisParameter())
            {
                thisType = calle.Arguments.First().Type.ResolvedType;
                thisMethods = thisType
                    .GetInstanceMethods()
                    .ToList();
            }
            else
            {
                thisType = call.MethodToCall.ContainingType.ResolvedType;
                thisMethods = thisType
                    .Methods.Where(m => !m.HasThisParameter())
                    .ToList();
            }
            var callMethodName = calle.Method.GetQualifiedName();
            var thisMethodsAnswerRank = thisMethods
                .Select(res => res.GetQualifiedName())
                .TakeWhile(r => !r.Equals(callMethodName))
                .Count() + 1;
            var thisMethodsCount = thisMethods.Count;
            if (thisMethodsAnswerRank > thisMethodsCount)
            {
                throw new Exception("Method not found in this.");
            }
            var argsMethodsCount = call.Arguments
                .Sum(a => a.Type.ResolvedType.GetInstanceMethods().Count());

            Logger.PrintLine("Call info tuple: "
                + string.Join(";", new string[] {
                            calle.Arguments.Count.ToString(),
                            calle.Method.Name.ToString(),
                            call.Type.ToString(),
                            instStaticStr,
                            thisType.ToString(),
                            thisType.GetNamespace().Length.ToString(),
                            thisType.GetNamespace().CommonPrefixLength(
                                // TODO Is this what was meant?
                                call.MethodToCall.ResolvedMethod
                                    .ContainingType.GetNamespace())
                                .ToString(),
                            thisMethodsAnswerRank.ToString(),
                            thisMethodsCount.ToString(),
                            argsMethodsCount.ToString(),
                            bestCorrectRank.ToString(),
                            bestCorrectRankStr,
                            (thisMethodsAnswerRank == -1 ? ""
                                : (thisMethodsAnswerRank - bestCorrectRank).ToString()),
                            bestTypedRank.ToString(),
                            bestTypedRankStr,
                            (thisMethodsAnswerRank == -1 ? ""
                                : (thisMethodsAnswerRank - bestTypedRank).ToString()),
                            "",//bestKeywordRank.ToString(),
                            "",//bestKeywordRankStr,
                            "",/*(thisMethodsAnswerRank == -1 ? ""
                                : (thisMethodsAnswerRank - bestKeywordRank).ToString()),*/
                            "",//bestCorrectRankRes.CorrectAnswerScoreDifference.ToString(),
                            (shortestGood != null ? shortestGood.ArgSubset.Count().ToString() : ""),
                            (shortestGood != null ? shortestGood.CorrectAnswerRank.ToString() : ""),
                            (shortestOkay != null ? shortestOkay.ArgSubset.Count().ToString() : ""),
                            (shortestOkay != null ? shortestOkay.CorrectAnswerRank.ToString() : ""),
                            bestCorrectRankRes.ComptuationTime.TotalSeconds.ToString(),
                        })
                );

            foreach (var res in allResults)
            {
                var query = res.Query;
                Logger.PrintHeader(query.ToString());
                var results = res.AlternateMethods;
                Logger.PrintLine("Time elapsed for arg subset (seconds): "
                    + res.ComptuationTime.TotalSeconds);
                var correctRank = res.CorrectAnswerRank;
                if (correctRank == int.MaxValue)
                {
                    Logger.PrintLine("??? Correct answer not found.");
                }
                else
                {
                    Logger.PrintLine("Correct answer rank: "
                        + correctRank + "/" + results.Count());
                    Logger.PrintLine("Typed correct answer rank: "
                        + res.CorrectTypedAnswerRank + "/" + results.Count());
                }
                /*
                foreach (var alt in results)
                {
                    if ((alt as MethodCall).Method.GetQualifiedName()
                        .Equals(calle.Method.GetQualifiedName()))
                    {
                        Logger.Print("CORRECT ");
                    }
                    if (alt.Type.ResolvedType.Equals(call.Type.ResolvedType))
                    {
                        Logger.Print("TYPED ");
                    }
                    Logger.PrintLine("ALT (" + alt.Score(context) + "): " + alt);
                }
                */
            }
        }
    }
}
